// Copyright 2023 Bloomberg Finance L.P.
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// mwcio_channelutil.t.cpp                                            -*-C++-*-
#include <mwcio_channelutil.h>

// TEST DRIVER
#include <mwctst_testhelper.h>

// MWC
#include <mwcio_resolveutil.h>

// BDE
#include <bdlb_bigendian.h>
#include <bdlbb_blob.h>
#include <bdlbb_blobutil.h>
#include <bdlbb_pooledblobbufferfactory.h>
#include <bsl_string.h>
#include <bsl_vector.h>
#include <bslmf_assert.h>
#include <bsls_assert.h>
#include <bsls_byteorder.h>

// NTC
#include <ntsa_error.h>
#include <ntsa_ipaddress.h>

// CONVENIENCE
using namespace BloombergLP;
using namespace bsl;
using namespace mwcio;

// ============================================================================
//                            TEST HELPERS UTILITY
// ----------------------------------------------------------------------------

namespace {

static const int k_MINIMUM_PACKET_LENGTH = sizeof(bdlb::BigEndianUint32);
BSLMF_ASSERT(k_MINIMUM_PACKET_LENGTH == sizeof(unsigned int));

// FUNCTIONS

/// Load into the specified `str` a string of random alphanumeric characters
/// of the specified `len` size.
static void generateRandomString(bsl::string* str, size_t len)
{
    static const char k_ALPHANUM[] = "0123456789"
                                     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                                     "abcdefghijklmnopqrstuvwxyz";

    for (size_t i = 0; i < len; ++i) {
        str->push_back(k_ALPHANUM[rand() % (sizeof(k_ALPHANUM) - 1)]);
    }
}

/// Add to the specified `out` blob a fully formatted packet composed of a 4
/// bytes packet length containing the specified `length` followed by
/// randomly generated bytes so that the total added bytes are of `blobSize`
/// size.
void appendToInputBlob(bdlbb::Blob* out, size_t length, size_t blobSize)
{
    // PRECONDITIONS
    BSLS_ASSERT_OPT(out);

    // First, append the length
    const unsigned int len = BSLS_BYTEORDER_HOST_U32_TO_BE(length);
    bdlbb::BlobUtil::append(out,
                            reinterpret_cast<const char*>(&len),
                            sizeof(unsigned int));

    // Finally, append some random bytes
    bsl::string randomStr(s_allocator_p);
    generateRandomString(&randomStr, blobSize - k_MINIMUM_PACKET_LENGTH);
    bdlbb::BlobUtil::append(out,
                            randomStr.data(),
                            blobSize - k_MINIMUM_PACKET_LENGTH);
}

}  // close unnamed namespace

// ============================================================================
//                                    TESTS
// ----------------------------------------------------------------------------

static void test1_handleRead_singlePacket()
{
    mwctst::TestHelper::printTestName("HANDLE READ - SINGLE PACKET");

    {
        PVV("INVALID BLOB - NO HEADER");
        bdlbb::PooledBlobBufferFactory bufferFactory(1024, s_allocator_p);
        bdlbb::Blob                    packet(&bufferFactory, s_allocator_p);
        bdlbb::Blob                    input(&bufferFactory, s_allocator_p);
        int                            numNeeded = 0;

        const int rc = mwcio::ChannelUtil::handleRead(&packet,
                                                      &numNeeded,
                                                      &input);
        ASSERT_EQ(rc, 0);
        ASSERT_EQ(packet.length(), 0);
        ASSERT_EQ(input.length(), 0);
        ASSERT_EQ(numNeeded, k_MINIMUM_PACKET_LENGTH);
    }

    {
        PVV("INVALID BLOB - BOGUS HEADER PACKET TOO SMALL");
        bdlbb::PooledBlobBufferFactory bufferFactory(1024, s_allocator_p);
        bdlbb::Blob                    packet(&bufferFactory, s_allocator_p);
        bdlbb::Blob                    input(&bufferFactory, s_allocator_p);
        int                            numNeeded = 0;

        const int totalLength = k_MINIMUM_PACKET_LENGTH + 12;
        appendToInputBlob(&input, 3, totalLength);
        const int rc = mwcio::ChannelUtil::handleRead(&packet,
                                                      &numNeeded,
                                                      &input);
        ASSERT_EQ(rc, -1);
        ASSERT_EQ(packet.length(), 0);
        ASSERT_EQ(input.length(), totalLength);
        ASSERT_EQ(numNeeded, 0);
    }

    {
        PVV("VALID BLOB - INCOMPLETE INPUT BLOB");
        bdlbb::PooledBlobBufferFactory bufferFactory(1024, s_allocator_p);
        bdlbb::Blob                    packet(&bufferFactory, s_allocator_p);
        bdlbb::Blob                    input(&bufferFactory, s_allocator_p);
        int                            numNeeded = 0;

        const int totalLength = k_MINIMUM_PACKET_LENGTH + 12;
        appendToInputBlob(&input, totalLength, totalLength - 1);
        const int rc = mwcio::ChannelUtil::handleRead(&packet,
                                                      &numNeeded,
                                                      &input);
        ASSERT_EQ(rc, 0);
        ASSERT_EQ(packet.length(), 0);
        ASSERT_EQ(input.length(), totalLength - 1);
        ASSERT_EQ(numNeeded, totalLength);
    }

    {
        PVV("VALID BLOB - HEADER IN ONE BUFFER");
        bdlbb::PooledBlobBufferFactory bufferFactory(1024, s_allocator_p);
        bdlbb::Blob                    packet(&bufferFactory, s_allocator_p);
        bdlbb::Blob                    input(&bufferFactory, s_allocator_p);
        int                            numNeeded = 0;

        const int totalLength = k_MINIMUM_PACKET_LENGTH + 12;
        appendToInputBlob(&input, totalLength, totalLength);
        const int rc = mwcio::ChannelUtil::handleRead(&packet,
                                                      &numNeeded,
                                                      &input);
        ASSERT_EQ(rc, 0);
        ASSERT_EQ(packet.length(), totalLength);
        ASSERT_EQ(input.length(), 0);
        ASSERT_EQ(numNeeded, k_MINIMUM_PACKET_LENGTH);
    }

    {
        PVV("VALID BLOB - HEADER IN MULTIPLE BUFFERS");
        bdlbb::PooledBlobBufferFactory bufferFactory(2, s_allocator_p);
        bdlbb::Blob                    packet(&bufferFactory, s_allocator_p);
        bdlbb::Blob                    input(&bufferFactory, s_allocator_p);
        int                            numNeeded = 0;

        const int totalLength = k_MINIMUM_PACKET_LENGTH + 12;
        appendToInputBlob(&input, totalLength, totalLength);
        const int rc = mwcio::ChannelUtil::handleRead(&packet,
                                                      &numNeeded,
                                                      &input);
        ASSERT_EQ(rc, 0);
        ASSERT_EQ(packet.length(), totalLength);
        ASSERT_EQ(input.length(), 0);
        ASSERT_EQ(numNeeded, k_MINIMUM_PACKET_LENGTH);
    }
}

static void test2_handleRead_multiplePackets()
{
    mwctst::TestHelper::printTestName("HANDLE READ - MULTIPLE PACKETS");

    {
        PVV("INVALID BLOB - NO HEADER");
        bdlbb::PooledBlobBufferFactory bufferFactory(1024, s_allocator_p);
        bsl::vector<bdlbb::Blob>       packets(s_allocator_p);
        bdlbb::Blob                    input(&bufferFactory, s_allocator_p);
        int                            numNeeded = 0;

        const int totalLength = k_MINIMUM_PACKET_LENGTH + 12;
        appendToInputBlob(&input, totalLength, totalLength);
        const int rc = mwcio::ChannelUtil::handleRead(&packets,
                                                      &numNeeded,
                                                      &input);
        ASSERT_EQ(rc, 0);
        ASSERT_EQ(packets.size(), 1U);
        ASSERT_EQ(packets[0].length(), totalLength)
        ASSERT_EQ(input.length(), 0);
        ASSERT_EQ(numNeeded, k_MINIMUM_PACKET_LENGTH);
    }

    {
        PVV("INVALID BLOB - BOGUS HEADER PACKET LENGTH TOO SMALL");
        bdlbb::PooledBlobBufferFactory bufferFactory(1024, s_allocator_p);
        bsl::vector<bdlbb::Blob>       packets(s_allocator_p);
        bdlbb::Blob                    input(&bufferFactory, s_allocator_p);
        int                            numNeeded = 0;

        const int totalLength = k_MINIMUM_PACKET_LENGTH + 12;
        appendToInputBlob(&input, totalLength, totalLength);
        appendToInputBlob(&input, 3, totalLength);
        const int rc = mwcio::ChannelUtil::handleRead(&packets,
                                                      &numNeeded,
                                                      &input);
        ASSERT_EQ(rc, -1);
        ASSERT_EQ(packets.size(), 1U);
        ASSERT_EQ(packets[0].length(), totalLength);
        ASSERT_EQ(input.length(), totalLength);
        ASSERT_EQ(numNeeded, 0);
    }

    {
        PVV("VALID BLOB - INCOMPLETE INPUT BLOB");
        bdlbb::PooledBlobBufferFactory bufferFactory(1024, s_allocator_p);
        bsl::vector<bdlbb::Blob>       packets(s_allocator_p);
        bdlbb::Blob                    input(&bufferFactory, s_allocator_p);
        int                            numNeeded = 0;

        const int totalLength = k_MINIMUM_PACKET_LENGTH + 12;
        appendToInputBlob(&input, totalLength, totalLength);
        appendToInputBlob(&input, totalLength, totalLength - 1);
        const int rc = mwcio::ChannelUtil::handleRead(&packets,
                                                      &numNeeded,
                                                      &input);
        ASSERT_EQ(rc, 0);
        ASSERT_EQ(packets.size(), 1U);
        ASSERT_EQ(packets[0].length(), totalLength);
        ASSERT_EQ(input.length(), totalLength - 1);
        ASSERT_EQ(numNeeded, totalLength);
    }

    {
        PVV("VALID BLOB - HEADER IN ONE BUFFER");
        bdlbb::PooledBlobBufferFactory bufferFactory(1024, s_allocator_p);
        bsl::vector<bdlbb::Blob>       packets(s_allocator_p);
        bdlbb::Blob                    input(&bufferFactory, s_allocator_p);
        int                            numNeeded = 0;

        const int totalLength = k_MINIMUM_PACKET_LENGTH + 12;
        appendToInputBlob(&input, totalLength, totalLength);
        appendToInputBlob(&input, totalLength - 1, totalLength - 1);
        appendToInputBlob(&input, totalLength - 2, totalLength - 2);
        const int rc = mwcio::ChannelUtil::handleRead(&packets,
                                                      &numNeeded,
                                                      &input);
        ASSERT_EQ(rc, 0);
        ASSERT_EQ(packets.size(), 3U);
        ASSERT_EQ(packets[0].length(), totalLength);
        ASSERT_EQ(packets[1].length(), totalLength - 1);
        ASSERT_EQ(packets[2].length(), totalLength - 2);
        ASSERT_EQ(input.length(), 0);
        ASSERT_EQ(numNeeded, k_MINIMUM_PACKET_LENGTH);
    }

    {
        PVV("VALID BLOB - HEADER IN MULTIPLE BUFFERS");
        bdlbb::PooledBlobBufferFactory bufferFactory(2, s_allocator_p);
        bsl::vector<bdlbb::Blob>       packets(s_allocator_p);
        bdlbb::Blob                    input(&bufferFactory, s_allocator_p);
        int                            numNeeded = 0;

        const int totalLength = k_MINIMUM_PACKET_LENGTH + 12;
        appendToInputBlob(&input, totalLength, totalLength);
        appendToInputBlob(&input, totalLength - 2, totalLength - 2);
        const int rc = mwcio::ChannelUtil::handleRead(&packets,
                                                      &numNeeded,
                                                      &input);
        ASSERT_EQ(rc, 0);
        ASSERT_EQ(packets.size(), 2U);
        ASSERT_EQ(packets[0].length(), totalLength);
        ASSERT_EQ(packets[1].length(), totalLength - 2);
        ASSERT_EQ(input.length(), 0);
        ASSERT_EQ(numNeeded, k_MINIMUM_PACKET_LENGTH);
    }

    {
        PVV("VALID BLOB - LEFTOVER BYTES");
        bdlbb::PooledBlobBufferFactory bufferFactory(1024, s_allocator_p);
        bsl::vector<bdlbb::Blob>       packets(s_allocator_p);
        bdlbb::Blob                    input(&bufferFactory, s_allocator_p);
        int                            numNeeded = 0;

        const int totalLength = k_MINIMUM_PACKET_LENGTH + 12;
        appendToInputBlob(&input, totalLength, totalLength);
        appendToInputBlob(&input, totalLength, k_MINIMUM_PACKET_LENGTH);
        const int rc = mwcio::ChannelUtil::handleRead(&packets,
                                                      &numNeeded,
                                                      &input);
        ASSERT_EQ(rc, 0);
        ASSERT_EQ(packets.size(), 1U);
        ASSERT_EQ(packets[0].length(), totalLength);
        ASSERT_EQ(input.length(), k_MINIMUM_PACKET_LENGTH);
        ASSERT_EQ(numNeeded, totalLength);
    }
}

static void test3_isLocalHost()
{
    mwctst::TestHelper::printTestName("IS LOCAL HOST - BREATHING TEST");

    // mwcio::ChannelUtil::isLocalHost(const ntsa::IpAddress&) internally uses
    // default allocator
    s_ignoreCheckDefAlloc = true;

    {
        PVV("'LOCALHOST' - TRUE");
        ASSERT(mwcio::ChannelUtil::isLocalHost("localhost"));
        ASSERT(mwcio::ChannelUtil::isLocalHost("LoCaLhOsT"));

        PVV("'WWW.WIKIPEDIA.ORG' - FALSE")
        ASSERT(!mwcio::ChannelUtil::isLocalHost("www.wikipedia.org"));
    }

    {
        PVV("'LOCAL IP ADDRESSES' - TRUE");
        bsl::vector<ntsa::IpAddress> localIPs(s_allocator_p);

        ASSERT_EQ(mwcio::ResolveUtil::getLocalIpAddress(&localIPs).code(),
                  ntsa::Error::e_OK);

        bsl::vector<ntsa::IpAddress>::const_iterator it;
        for (it = localIPs.begin(); it != localIPs.end(); ++it) {
            PVV(*it);
            ASSERT(mwcio::ChannelUtil::isLocalHost(*it));
        }
    }
}

// ============================================================================
//                                 MAIN PROGRAM
// ----------------------------------------------------------------------------

int main(int argc, char* argv[])
{
    TEST_PROLOG(mwctst::TestHelper::e_DEFAULT);

    switch (_testCase) {
    case 0:
    case 3: test3_isLocalHost(); break;
    case 2: test2_handleRead_multiplePackets(); break;
    case 1: test1_handleRead_singlePacket(); break;
    default: {
        cerr << "WARNING: CASE '" << _testCase << "' NOT FOUND." << endl;
        s_testStatus = -1;
    } break;
    }

    TEST_EPILOG(mwctst::TestHelper::e_CHECK_DEF_GBL_ALLOC);
}
